#include "audio_manager.h"

#include "application/game_application.h"
#include "application/game_rms.h"
#include "script/scripthelper.h"
#ifdef _ENABLE_LUABIND
#include <luabind/luabind.hpp>
#endif //_ENABLE_LUABIND

#include "util/log.h"				// log
#include "util/common_macros.h"		// macros
#include "util/mmgr.h"		    

using namespace audio;

AudioManager::AudioManager() : m_pData(NULL),
	m_pThemes(NULL),
	m_pStings(NULL),
	m_Current(0),
	m_CurTheme(-1),
	m_ThemePlayC(0),
	m_IsPlayingSting(false),
	m_pLoops(NULL),
	m_pEvents(NULL),
	m_bDeleteData(true)
{
}

AudioManager::~AudioManager()
{
	AUDIO().StopAll();

	if (m_bDeleteData) UTIL_SAFE_DELETE(m_pData);

	UTIL_SAFE_DELETEA(m_pThemes);
	UTIL_SAFE_DELETEA(m_pStings);
	UTIL_SAFE_DELETEA(m_pLoops);
	UTIL_SAFE_DELETEA(m_pEvents);
}

AudioManager& AudioManager::Instance()
{
	static AudioManager inst;
	return inst;
}

void AudioManager::Load(AudioData* pData, bool DeleteData)
{
	if (!pData) return;
	m_pData = pData;
	m_bDeleteData = DeleteData;

	int MusicC = m_pData->GetMusicC();
	int StingC = m_pData->GetStingC();
	int AudioC = m_pData->GetAudioEventC();
	int LoopC = m_pData->GetLoopEventC();

	// init audio sound bank
	AUDIO().Init( m_pData->GetBasePath() + m_pData->GetSoundBank() );
	if (GameRMS* pRMS = g_pApp->GetRMS())
	{
		AUDIO().SetMusicVolume( (int)pRMS->GetMusicVolume() );
		AUDIO().SetSFXVolume( (int)pRMS->GetSoundVolume() );
		_LOG(MSG_INIT, "Audio Manager Init");
	}

	// precache all music and stings
	if (MusicC)
	{
		m_pThemes = new audio::AudioFile[MusicC];
		for (int i = 0; i < MusicC; i++)
		{
			m_pThemes[i] = AUDIO().LoadSound( Cat(m_pData->MapMusicToString(i)), m_pData->MusicRepeats(i) );
		}
	}

	if (StingC)
	{
		m_pStings = new audio::AudioFile[StingC];
		for (int i = 0; i < StingC; i++)
		{
			m_pStings[i] = AUDIO().LoadSound( Cat(m_pData->MapStingToString(i)), false );
		}
	}

	if (AudioC)
	{
		m_pEvents = new int[AudioC];
		memset(m_pEvents, 0, sizeof(int)*AudioC);
	}

	if (LoopC)
	{
		m_pLoops = new LOOP_DATA[LoopC];
		memset(m_pLoops, 0, sizeof(LOOP_DATA)*LoopC);
	}

	if (m_pThemes && MusicC)
	{
		// play main theme
		unsigned int id = m_pData->GetMainThemeID();
		m_Current = AUDIO().PlayMusicH(m_pThemes[id]);
		m_CurTheme = (int)id;
		m_ThemePlayC = 1;
	}

	_LOG(MSG_INIT, "Audio Manager Cache Complete");
}

void AudioManager::Tick(float dt)
{
	if (!m_pData) return;

	// music
	if (!IsPlaying())
	{
		if (m_IsPlayingSting)
		{
			m_pData->OnStingComplete(m_CurTheme);
			m_IsPlayingSting = false;
		}
		else if (m_ThemePlayC++)
		{
			m_pData->OnMusicRepeat(m_CurTheme);
		}
		ResumeCurrent();
	}

	// maintain loops
	if (m_pLoops)
	{
		for (int i = 0; i < m_pData->GetLoopEventC(); i++)
		{
			LOOP_DATA& ld = m_pLoops[i];
			if (ld.Handle && !AUDIO().IsPlaying(ld.Handle))
				ld.Handle = AUDIO().PlayEvent( m_pData->MapLoopToString(i) );
		}
	}

	// audio sfx
	TriggerSounds();
}

bool AudioManager::IsPlaying()
{
	if (m_Current && AUDIO().IsPlaying(m_Current))
		return true;

	return false;
}

void AudioManager::SetTheme(unsigned int ID, bool PlayNow)
{
	if (!m_pData || !m_pThemes) return;

	if (m_CurTheme != ID)
	{
		m_CurTheme = ID;
		m_ThemePlayC = 0;

		if (PlayNow)
		{
			m_IsPlayingSting = false;
			m_ThemePlayC++;
			ResumeCurrent();
		}
	}
}

void AudioManager::PlaySting(unsigned int ID)
{
	if (!m_pData) return;
	if (m_pStings && m_pStings[ID])
	{
		m_Current = AUDIO().PlayMusicH( m_pStings[ID] );
		m_ThemePlayC = 0;
		m_IsPlayingSting = true;
	}
}

void AudioManager::ResumeCurrent()
{
	if (!m_pData) return;
	if (m_pThemes && (m_CurTheme >= 0) && m_pThemes[m_CurTheme])
		m_Current = AUDIO().PlayMusicH( m_pThemes[m_CurTheme] );
}

void AudioManager::PlaySoundEvent(unsigned int ID, bool TriggerNow)
{
	if (!m_pData) return;
	if (m_pEvents)
	{
		m_pEvents[ID] = 1;

		if (TriggerNow)
			TriggerSounds();
	}
}

void AudioManager::PlayLoop(unsigned int ID)
{
	if (!m_pData) return;
	if (m_pLoops)
	{
		m_pLoops[ID].Count++;
		if (!m_pLoops[ID].Handle)
		{
			m_pLoops[ID].Handle = AUDIO().PlayEvent( m_pData->MapLoopToString(ID) );
		}
	}
}

void AudioManager::StopLoop(unsigned int ID, bool StopAll)
{
	if (!m_pData) return;
	if (m_pLoops)
	{
		if (StopAll)
			m_pLoops[ID].Count = 0;
		else if (m_pLoops[ID].Count > 0)
			m_pLoops[ID].Count--;

		if (!m_pLoops[ID].Count)
			m_pLoops[ID].Handle = 0;
	}
}

void AudioManager::TriggerSounds()
{
	if (m_pEvents)
	{
		for (int i = 0; i < m_pData->GetAudioEventC(); i++)
		{
			if (m_pEvents[i])
			{
				AUDIO().PlayEvent( m_pData->MapAudioToString(i) );
				m_pEvents[i] = 0;
			}
		}
	}
}

std::string AudioManager::Cat(const std::string& s)
{
	return m_pData->GetBasePath() + s + m_pData->GetSoundExt();
}



static AudioManager* GetAudioMan()
{
	return &AudioManager::Instance();
}

void AudioManager::SetThemeStr(const std::string& name, bool PlayNow)
{
	if (!m_pData) return;
	SetTheme( m_pData->MapStringToMusic(name), PlayNow );
}

void AudioManager::PlayStingStr(const std::string& name)
{
	if (!m_pData) return;
	PlaySting( m_pData->MapStringToSting(name) );
}

void AudioManager::PlaySoundStr(const std::string& name, bool TriggerNow)
{
	if (!m_pData) return;
	PlaySoundEvent( m_pData->MapStringToAudio(name), TriggerNow );
}

void AudioManager::PlayLoopStr(const std::string& name)
{
	if (!m_pData) return;
	PlayLoop( m_pData->MapStringToLoop(name) );
}

void AudioManager::StopLoopStr(const std::string& name, bool StopAll)
{
	if (!m_pData) return;
	StopLoop( m_pData->MapStringToLoop(name), StopAll );
}


void AudioManager::Register(LuaPlus::LuaState* _pScript)
{
#ifdef _ENABLE_LUABIND
	lua_State * L = _pScript->GetCState ( );

	luabind::module(L)
    [
    luabind::class_<AudioManager>("AudioManager")
		.def("SetTheme",					&AudioManager::SetThemeStr)
		.def("SetSting",					&AudioManager::PlayStingStr)
		.def("PlaySound",					&AudioManager::PlaySoundStr)
		.def("PlayLoop",					&AudioManager::PlayLoopStr)
		.def("StopLoop",					&AudioManager::StopLoopStr)
		.def("ResumeCurrent",				&AudioManager::ResumeCurrent)
	];
   
	// Access file from config
	luabind::module(L)
    [
	 luabind::def("GetAudioMan",	&GetAudioMan , luabind::detail::null_type() )	 
	 ];
#endif	
}
